package com.cloudcentral.config;

import com.cloudcentral.service.UserService;
import com.cloudcentral.util.Result;
import com.cloudcentral.util.WebUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.UUID;

/**
 * Create by Administrator
 * 2023/11/10 16:49
 */
@Configuration
public class AuthorizationServerConfig {
  @Value("${server.port}")
  private String serverPort;
  private final UserService userService;

  @Autowired
  public AuthorizationServerConfig(UserService userService) {
    this.userService = userService;
  }

  @Bean
  @Order(0)
  public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {

    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    return http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
        .authorizationEndpoint(endpoint -> endpoint.consentPage("/consent.html")
            //.authorizationResponseHandler((request, response, authentication) -> WebUtils.writeJson(response, Result.success("授权成功")))
            .errorResponseHandler((request, response, exception) -> WebUtils.writeJson(response, Result.fail(exception.getMessage()))))

        .tokenRevocationEndpoint(endpoint -> endpoint
            .revocationResponseHandler((request, response, authentication) -> WebUtils.writeJson(response, Result.ok("注销成功")))
            .errorResponseHandler((request, response, exception) -> WebUtils.writeJson(response, Result.fail(exception.getMessage())))
        )

        .oidc(oidc -> oidc.userInfoEndpoint(userinfo -> userinfo
            .userInfoMapper(context -> {
              OAuth2Authorization authorization = context.getAuthorization();
              String principal = authorization.getPrincipalName();
              Set<String> scopes = context.getAuthorization().getAuthorizedScopes();
              return userService.loadUserInfo(principal, scopes);
            })
            .userInfoResponseHandler((request, response, authentication) ->
                WebUtils.writeJson(response, Result.ok(((OidcUserInfoAuthenticationToken) authentication).getUserInfo().getClaims())))))
        .and()

        .oauth2ResourceServer().jwt()
        .and()
        .authenticationEntryPoint((request, response, exception) -> WebUtils.writeJson(response, Result.invalid()))
        .accessDeniedHandler((request, response, exception) -> WebUtils.writeJson(response, Result.denied()))
        .and()

        .exceptionHandling()
        .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login.html"))
        .accessDeniedHandler((request, response, exception) -> WebUtils.writeJson(response, Result.denied()))
        .and()

        .build();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

  @Bean
  public JdbcTemplate jdbcTemplate(DataSource dataSource) {
    return new JdbcTemplate(dataSource);
  }

  @Bean
  public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository repository) {
    return new JdbcOAuth2AuthorizationService(jdbcTemplate, repository);
  }

  /**
   * 基于数据库存储用户、客户端授权关联信息
   */
  @Bean
  public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository repository) {
    return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, repository);
  }

  /**
   * 客户端配置，基于内存
   */
  @Bean
  public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
    //http://127.0.0.1:9000/security/oauth2/authorize?client_id=cloudcentral&scope=openid+profile+phone&response_type=code&redirect_uri=http://localhost:8066
    JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
    jdbcRegisteredClientRepository.save(RegisteredClient.withId("cloudcentral")
        .clientId("cloudcentral")
        .clientSecret(passwordEncoder().encode("cloudcentral"))
        .clientName("超视距云服务中心")
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantTypes(grantTypes -> grantTypes.addAll(List.of(
            AuthorizationGrantType.CLIENT_CREDENTIALS,
            AuthorizationGrantType.AUTHORIZATION_CODE,
            AuthorizationGrantType.REFRESH_TOKEN,
            AuthorizationGrantType.JWT_BEARER)))
        .redirectUris(uris -> uris.addAll(List.of(
            "http://localhost:8066",
            "http://127.0.0.1:8066",
            "http://localhost:8077",
            "http://127.0.0.1:8077",
            "http://140.210.217.137"
        )))
        .scope(OidcScopes.OPENID) // OIDC
        .scope(OidcScopes.PROFILE)
        .scope(OidcScopes.PHONE)
        .clientSettings(ClientSettings.builder()
            .requireAuthorizationConsent(false) // 需要授权同意，false表示自动授权（静默授权）
            .requireProofKey(false) // 是否需要需要验证密钥
            .jwkSetUrl("http://127.0.0.1:" + serverPort + "/oauth2/jwks")
            .build())
        .tokenSettings(TokenSettings.builder()
            .accessTokenTimeToLive(Duration.ofMinutes(30)) // 访问令牌有效期（默认5分钟）
            .refreshTokenTimeToLive(Duration.ofDays(1)) // 刷新令牌有效期（默认60分钟）
            .authorizationCodeTimeToLive(Duration.ofMinutes(10)) // 授权码有效期（默认5分钟）
            .reuseRefreshTokens(true)
            .build())
        .build());// 初始化时可以直接保存一个客户端到数据库中
    return jdbcRegisteredClientRepository;
  }

  /**
   * 解码签名访问令牌
   */
  @Bean
  public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
    return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
  }

  /**
   * 配置Spring授权服务器
   */
  @Bean
  public AuthorizationServerSettings authorizationServerSettings() {
    return AuthorizationServerSettings.builder()
        // 发行者（一般设置为访问域名）
        .issuer("http://127.0.0.1:" + serverPort) // 默认 http://localhost:8080
        // 授权端点路径
        .authorizationEndpoint("/oauth2/authorize") // 默认 /oauth2/authorize
        // 令牌端点路径
        .tokenEndpoint("/oauth2/token") // 默认 /oauth2/token
        // 令牌内省端点
        .tokenIntrospectionEndpoint("/oauth2/introspect") // 默认 /oauth2/introspect
        // 令牌销毁端点
        .tokenRevocationEndpoint("/oauth2/revoke")// 默认 /oauth2/revoke
        // 获取JWK端点
        .jwkSetEndpoint("/oauth2/jwks")
        // OIDC 用户信息端点
        .oidcUserInfoEndpoint("/oauth2/userinfo")
        .oidcClientRegistrationEndpoint("/oauth2/register")
        .build();
  }

  /**
   * 访问令牌签名
   */
  @Bean
  public JWKSource<SecurityContext> jwkSource() {
    try {
      KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
      keyPairGenerator.initialize(2048);
      KeyPair keyPair = keyPairGenerator.generateKeyPair();
      RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
      RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
      RSAKey rsaKey = new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
      JWKSet jwkSet = new JWKSet(rsaKey);
      return new ImmutableJWKSet<>(jwkSet);
    } catch (Exception e) {
      throw new IllegalStateException(e);
    }
  }
}
